﻿using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using M = System.Math;
//5/1a/s/px

namespace DynamicGeometry
{
    public class CoordinateSystem : IMovable
    {
        public CoordinateSystem(Drawing drawing)
        {
            Check.NotNull(drawing);

            Drawing = drawing;
            UnitLength = 48;

            Drawing.SizeChanged += Drawing_SizeChanged;
            Origin = new Point(Canvas.ActualWidth / 2, Canvas.ActualHeight / 2);
        }

        public Drawing Drawing { get; set; }

        const int zoomStep = 2;
        public void ZoomIn()
        {
            UnitLength += zoomStep;
        }

        public void ZoomOut()
        {
            UnitLength -= zoomStep;
        }

        #region Bounds

        public Canvas Canvas
        {
            get
            {
                return Drawing.Canvas;
            }
        }

        public Point[] LogicalViewportVertices { get; set; }
        public double MinimalVisibleX { get; set; }
        public double MinimalVisibleY { get; set; }
        public double MaximalVisibleX { get; set; }
        public double MaximalVisibleY { get; set; }

        void Drawing_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Recalculate();
        }

        private void Recalculate()
        {
            LogicalViewportVertices = GetViewportVerticesInLogical();
            MinimalVisibleX = LogicalViewportVertices.Min(p => p.X);
            MinimalVisibleY = LogicalViewportVertices.Min(p => p.Y);
            MaximalVisibleX = LogicalViewportVertices.Max(p => p.X);
            MaximalVisibleY = LogicalViewportVertices.Max(p => p.Y);
            Drawing.Figures.Recalculate();
        }

        public Point[] GetViewportVerticesInLogical()
        {
            Point[] result = new Point[4];
            double width = Canvas.ActualWidth;
            double height = Canvas.ActualHeight;
            if (!width.IsValidPositiveValue() || !height.IsValidPositiveValue())
            {
                return LogicalViewportVertices;
            }
            result[0] = ToLogical(new Point());
            result[1] = ToLogical(new Point(width, 0));
            result[2] = ToLogical(new Point(width, height));
            result[3] = ToLogical(new Point(0, height));
            return result;
        }

        public IEnumerable<double> GetVisibleXPoints()
        {
            for (var x = M.Ceiling(MinimalVisibleX); x <= M.Floor(MaximalVisibleX); x++)
            {
                yield return x;
            }
        }

        public IEnumerable<double> GetVisibleYPoints()
        {
            for (var y = M.Ceiling(MinimalVisibleY); y <= M.Floor(MaximalVisibleY); y++)
            {
                yield return y;
            }
        }

        #endregion

        #region Coordinate transforms

        private double unitLength;
        /// <summary>
        /// How many pixels are in a logical unit?
        /// </summary>
        public double UnitLength
        {
            get
            {
                return unitLength;
            }
            set
            {
                if (value < 2 || value > 1000)
                {
                    return;
                }
                unitLength = value;
                Recalculate();
            }
        }

        private Point mOrigin;
        /// <summary>
        /// Origin is in physical coordinates (for 800x600 it will usually be (400;300))
        /// </summary>
        public Point Origin
        {
            get
            {
                return mOrigin;
            }
            private set
            {
                mOrigin = value;
                Recalculate();
            }
        }

        public double CursorTolerance
        {
            get
            {
                return ToLogical(Math.CursorTolerance);
            }
        }

        public virtual Point ToLogical(Point physicalPoint)
        {
            return new Point(
                 (physicalPoint.X - Origin.X) / UnitLength,
                -(physicalPoint.Y - Origin.Y) / UnitLength);
        }

        public IEnumerable<Point> ToLogical(IEnumerable<Point> physicalPoints)
        {
            return physicalPoints.Select(p => ToLogical(p));
        }

        public PointPair ToLogical(PointPair pointPair)
        {
            var result = new PointPair(ToLogical(pointPair.P1), ToLogical(pointPair.P2));
            if (result.P1.X > result.P2.X)
            {
                result = result.Reverse;
            }
            if (result.P1.Y > result.P2.Y)
            {
                var temp = result.P2.Y;
                result.P2.Y = result.P1.Y;
                result.P1.Y = temp;
            }
            return result;
        }

        public virtual double ToLogical(double length)
        {
            return length / UnitLength;
        }

        public virtual Point ToPhysical(Point logicalPoint)
        {
            return new Point(
                Origin.X + logicalPoint.X * UnitLength,
                Origin.Y - logicalPoint.Y * UnitLength);
        }

        public PointPair ToPhysical(PointPair logicalPointPair)
        {
            return new PointPair(ToPhysical(logicalPointPair.P1), ToPhysical(logicalPointPair.P2));
        }

        public IEnumerable<Point> ToPhysical(IEnumerable<Point> logicalPoints)
        {
            return logicalPoints.Select(p => ToPhysical(p));
        }

        public virtual double ToPhysical(double length)
        {
            return length * UnitLength;
        }

        #endregion

        #region IMovable Members

        public void MoveTo(Point position)
        {
            Origin = ToPhysical(position);
        }

        public Point Coordinates
        {
            get { return new Point(); }
        }

        #endregion
    }
}
